<template>
  <div class="qr-scanner">
    <div class="scanner-container" v-if="showScanner">
      <qrcode-stream 
        @decode="onDecode" 
        @init="onInit"
        @camera-on="onCameraOn"
        @camera-off="onCameraOff"
        :paused="!isActive"
        :constraints="{
          facingMode: facingMode,
          aspectRatio: { min: 1, max: 2 },
          width: { min: 720 },
          height: { min: 720 }
        }"
        class="qrcode-stream"
        ref="qrcodeStream"
      >
        <div class="overlay-element">
          <div class="camera-status" v-if="cameraStatus">{{ cameraStatus }}</div>
          <div class="scanning-area">
            <div class="corner-element"></div>
            <div class="corner-element"></div>
            <div class="corner-element"></div>
            <div class="corner-element"></div>
          </div>
          <div class="scanning-tip">
            {{ scanningTip }}
          </div>
          <div class="camera-controls">
            <el-button 
              type="primary" 
              circle 
              @click="switchCamera" 
              class="switch-camera-btn"
              title="切换摄像头"
            >
              <i class="el-icon-refresh"></i>
            </el-button>
          </div>
          <div class="upload-btn">
            <input
              type="file"
              accept="image/*"
              @change="onFileChange"
              ref="fileInput"
              class="file-input"
            />
            <el-button type="primary" @click="triggerFileInput" class="upload-button">
              <i class="el-icon-upload"></i>
              上传图片扫描
            </el-button>
          </div>
        </div>
      </qrcode-stream>
    </div>
    
    <!-- 扫描结果对话框 -->
    <el-dialog
      v-model="dialogVisible"
      :title="dialogTitle"
      width="90%"
      :show-close="false"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :lock-scroll="false"
      custom-class="scan-dialog"
    >
      <div class="dialog-content">
        <el-result
          :icon="resultIcon"
          :title="resultTitle"
          :sub-title="resultMessage"
        >
          <template #extra>
            <el-button @click="handleClose">关闭</el-button>
            <el-button type="primary" @click="handleRetry" v-if="scanFailed">
              重试
            </el-button>
          </template>
        </el-result>
      </div>
    </el-dialog>
  </div>
</template>

<script>
// 使用选项式API定义组件
import { QrcodeStream } from 'vue-qrcode-reader'
import { qrCodeSign } from '@/api/student'
import { ElMessage } from 'element-plus'
import jsQR from 'jsqr'

export default {
  name: 'QRScanner',
  components: {
    QrcodeStream
  },
  props: {},
  emits: ['close'],
  data() {
    return {
      isActive: true,
      cameraStatus: '',
      scanningTip: '请将二维码对准扫描框',
      showScanner: true,
      dialogVisible: false,
      dialogTitle: '扫描结果',
      resultIcon: 'success',
      resultTitle: '',
      resultMessage: '',
      scanFailed: false,
      scanInterval: null,
      facingMode: 'environment' // 默认使用后置摄像头
    }
  },
  mounted() {
    this.ignoreResizeObserverErrors();
    
    // 延迟初始化扫描，避免在组件挂载阶段触发过多的 resize 事件
    setTimeout(() => {
      this.isActive = true;
    }, 300);
  },
  beforeUnmount() {
    this.stopIntervalScan();
    if (this._resizeHandlerCleanup) {
      this._resizeHandlerCleanup();
    }
  },
  methods: {
    // 开始定时扫描
    startIntervalScan() {
      if (this.scanInterval) {
        clearInterval(this.scanInterval);
      }
      
      // 使用 requestAnimationFrame 代替 setInterval 可以减少 ResizeObserver 错误
      const scan = () => {
        if (!this.isActive || !this.showScanner) {
          return;
        }
        
        // 获取视频元素
        const videoElement = this.$refs.qrcodeStream?.$el?.querySelector('video');
        if (!videoElement) {
          console.log('未找到视频元素，将在下一帧重试');
          this.scanInterval = requestAnimationFrame(scan);
          return;
        }
        
        try {
          // 确保视频已经准备好
          if (videoElement.readyState === videoElement.HAVE_ENOUGH_DATA) {
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            
            // 设置 canvas 尺寸为视频的实际尺寸
            canvas.width = videoElement.videoWidth;
            canvas.height = videoElement.videoHeight;

            // 绘制当前视频帧
            context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
            
            // 获取图像数据
            const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
            
            // 使用 jsQR 识别二维码
            const code = jsQR(imageData.data, imageData.width, imageData.height, {
              inversionAttempts: "dontInvert",
            });
            
            if (code) {
              console.log('通过扫描识别到二维码:', code.data);
              this.onDecode(code.data);
              this.stopIntervalScan();
              return;
            }
          }
        } catch (error) {
          console.error('扫描错误:', error);
        }
        
        // 继续下一帧扫描
        this.scanInterval = requestAnimationFrame(scan);
      };
      
      // 开始扫描
      this.scanInterval = requestAnimationFrame(scan);
    },

    // 停止定时扫描
    stopIntervalScan() {
      if (this.scanInterval) {
        cancelAnimationFrame(this.scanInterval);
        this.scanInterval = null;
      }
    },

    // 处理摄像头开启
    onCameraOn() {
      this.cameraStatus = '摄像头已就绪';
      ElMessage.success('摄像头已准备就绪');
      setTimeout(() => {
        this.cameraStatus = '';
      }, 2000);
      
      // 延迟一下再开始扫描，确保视频流已经完全准备好
      setTimeout(() => {
        this.startIntervalScan();
      }, 1000);
    },

    // 处理摄像头关闭
    onCameraOff() {
      this.cameraStatus = '摄像头已关闭';
      this.isActive = false;
      this.stopIntervalScan();
    },

    // 处理 ResizeObserver 错误
    ignoreResizeObserverErrors() {
      const resizeHandler = (err) => {
        if (err.message && err.message.includes('ResizeObserver')) {
          // 阻止错误传播
          err.stopImmediatePropagation();
          err.preventDefault();
          return false;
        }
      };
      
      // 使用捕获阶段监听错误
      window.addEventListener('error', resizeHandler, true);
      
      // 存储清理函数
      this._resizeHandlerCleanup = () => {
        window.removeEventListener('error', resizeHandler, true);
      };
    },

    onInit(promise) {
      this.cameraStatus = '正在初始化摄像头...';
      promise
        .then(({ capabilities }) => {
          console.log('摄像头初始化成功，支持的功能：', capabilities);
          
          if (capabilities.torch) {
            console.log('设备支持闪光灯');
          }
          
          this.isActive = true;
          this.scanningTip = '请将二维码对准扫描框';
        })
        .catch((error) => {
          console.error('摄像头初始化错误:', error);
          console.error('错误类型:', error.name);
          console.error('错误信息:', error.message);
          
          this.isActive = false;
          
          if (error.name === 'NotAllowedError') {
            this.cameraStatus = '无法访问摄像头';
            this.scanningTip = '请允许访问摄像头或使用图片上传';
            this.resultIcon = 'error';
            this.resultTitle = '无法访问摄像头';
            this.resultMessage = '请允许访问摄像头并重试。如果在手机浏览器中，请确保使用 HTTPS 访问。';
            this.dialogTitle = '摄像头访问被拒绝';
            this.scanFailed = true;
          } else if (error.name === 'NotFoundError') {
            this.cameraStatus = '未找到摄像头';
            this.scanningTip = '请使用图片上传功能';
            this.resultIcon = 'error';
            this.resultTitle = '未找到摄像头';
            this.resultMessage = '请确保设备有可用的摄像头，并允许浏览器访问。';
            this.dialogTitle = '摄像头错误';
            this.scanFailed = true;
          } else {
            this.cameraStatus = '摄像头错误';
            this.scanningTip = '请使用图片上传功能';
            this.resultIcon = 'error';
            this.resultTitle = '摄像头错误';
            this.resultMessage = error.message || '初始化摄像头时发生错误';
            this.dialogTitle = '摄像头错误';
            this.scanFailed = true;
          }
          
          this.dialogVisible = true;
          ElMessage.error('摄像头初始化失败，请尝试使用图片上传');
        });
    },

    async onDecode(result) {
      try {
        this.showScanner = false;
        this.dialogTitle = '扫描结果';
        
        console.log('扫描到二维码内容:', result);
        
        // 检查二维码内容格式
        const parts = result.split(':');
        if (parts.length !== 3) {
          throw new Error('二维码格式不正确，应为: activityId:secret:timestamp');
        }
        
        console.log('解析二维码内容:', {
          activityId: parts[0],
          secret: parts[1],
          timestamp: parts[2]
        });
        
        const response = await qrCodeSign({ qrCodeContent: result });
        console.log('签到请求响应:', response);
        
        if (response.code === 200) {
          this.resultIcon = 'success';
          this.resultTitle = '签到成功';
          this.resultMessage = '您已成功完成签到';
          this.scanFailed = false;
        } else {
          this.resultIcon = 'error';
          this.resultTitle = '签到失败';
          this.resultMessage = response.message || '请重试';
          this.scanFailed = true;
        }
      } catch (error) {
        console.error('签到错误:', error);
        console.error('错误详情:', {
          name: error.name,
          message: error.message,
          response: error.response?.data
        });
        
        this.resultIcon = 'error';
        this.resultTitle = '签到失败';
        this.resultMessage = error.response?.data?.message || '网络错误，请重试';
        this.scanFailed = true;
      } finally {
        this.dialogVisible = true;
      }
    },

    handleClose() {
      if (!this.scanFailed) {
        this.$emit('close', true); // 签到成功
      } else {
        this.$emit('close', false); // 签到失败
      }
    },

    handleRetry() {
      this.showScanner = true;
      this.dialogVisible = false;
      this.scanFailed = false;
      this.startIntervalScan(); // 重试时重新开始扫描
    },

    // 触发文件选择
    triggerFileInput() {
      this.$refs.fileInput.click();
    },

    // 处理文件选择
    async onFileChange(event) {
      const file = event.target.files[0];
      if (!file) return;

      try {
        const result = await this.readQRCodeFromImage(file);
        if (result) {
          this.onDecode(result);
        } else {
          ElMessage.error('未能在图片中识别出二维码');
        }
      } catch (error) {
        console.error('图片处理错误:', error);
        ElMessage.error('图片处理失败');
      }

      // 清除文件选择，允许重复选择同一文件
      event.target.value = '';
    },

    // 从图片中读取二维码
    readQRCodeFromImage(file) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = (e) => {
          const img = new Image();
          img.onload = () => {
            const canvas = document.createElement('canvas');
            const context = canvas.getContext('2d');
            canvas.width = img.width;
            canvas.height = img.height;
            context.drawImage(img, 0, 0);
            
            const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
            const code = jsQR(imageData.data, imageData.width, imageData.height);
            
            if (code) {
              resolve(code.data);
            } else {
              resolve(null);
            }
          };
          img.onerror = reject;
          img.src = e.target.result;
        };
        reader.onerror = reject;
        reader.readAsDataURL(file);
      });
    },

    // 切换前置/后置摄像头
    switchCamera() {
      // 停止当前扫描
      this.stopIntervalScan();
      
      // 切换摄像头
      this.facingMode = this.facingMode === 'environment' ? 'user' : 'environment';
      
      // 显示状态提示
      this.cameraStatus = '正在切换摄像头...';
      
      // 重新初始化摄像头需要一点时间，所以我们延迟一下再开始扫描
      setTimeout(() => {
        this.startIntervalScan();
      }, 1000);
    }
  }
}
</script>

<style scoped>
.qr-scanner {
  width: 100%;
  height: 100vh;
  background-color: #000;
  position: relative;
  contain: content;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.scanner-container {
  flex: 1;
  position: relative;
  contain: content;
  display: flex;
  justify-content: center;
  align-items: center;
}

.qrcode-stream {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.overlay-element {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 20px;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  padding-bottom: 80px; /* 为底部按钮预留空间 */
}

.camera-status {
  position: absolute;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  background-color: rgba(0, 0, 0, 0.7);
  color: #fff;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: 14px;
  z-index: 3;
}

.scanning-area {
  width: 280px;
  height: 280px;
  position: relative;
  background-color: transparent;
  border: 1px solid rgba(255, 255, 255, 0.3);
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
}

.scanning-tip {
  color: #fff;
  font-size: 16px;
  text-align: center;
  padding: 12px 20px;
  background-color: rgba(0, 0, 0, 0.7);
  border-radius: 8px;
  margin-top: 20px;
  z-index: 2;
}

.corner-element {
  position: absolute;
  width: 30px;
  height: 30px;
  border: 3px solid #fff;
}

.corner-element:nth-child(1) {
  top: 0;
  left: 0;
  border-right: none;
  border-bottom: none;
}

.corner-element:nth-child(2) {
  top: 0;
  right: 0;
  border-left: none;
  border-bottom: none;
}

.corner-element:nth-child(3) {
  bottom: 0;
  left: 0;
  border-right: none;
  border-top: none;
}

.corner-element:nth-child(4) {
  bottom: 0;
  right: 0;
  border-left: none;
  border-top: none;
}

.dialog-content {
  text-align: center;
}

:deep(.el-dialog__header) {
  margin: 0;
  padding: 12px 0;
  text-align: center;
  border-bottom: 1px solid #dcdfe6;
}

:deep(.el-dialog__body) {
  padding: 20px;
}

:deep(.el-result) {
  padding: 0;
}

:deep(.el-result__title) {
  margin-top: 10px;
}

:deep(.el-result__subtitle) {
  margin-top: 10px;
}

:deep(.el-result__extra) {
  margin-top: 20px;
}

.upload-btn {
  position: fixed;
  bottom: 40px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  width: 90%;
  max-width: 300px;
  padding: 0 20px;
  box-sizing: border-box;
}

.upload-button {
  width: 100%;
  height: 44px;
  font-size: 16px;
  border-radius: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}

.file-input {
  display: none;
}

/* 移动端适配 */
@media (max-width: 768px) {
  .qr-scanner {
    height: 100vh;
    width: 100vw;
  }
  
  .scanner-container {
    width: 100%;
    height: 100%;
  }
  
  .qrcode-stream {
    width: 100vw;
    height: 100%;
    object-fit: cover;
  }
  
  .overlay-element {
    padding-bottom: 100px;
  }
  
  .scanning-area {
    width: min(260px, 70vw);
    height: min(260px, 70vw);
  }
  
  .corner-element {
    width: 20px;
    height: 20px;
  }
  
  .scanning-tip {
    font-size: 14px;
    padding: 10px 16px;
    width: 80%;
    max-width: 300px;
  }
  
  .upload-btn {
    bottom: max(env(safe-area-inset-bottom, 20px) + 20px, 40px);
    width: calc(100% - 40px);
  }
  
  :deep(.scan-dialog) {
    width: 90% !important;
    max-width: 350px !important;
    margin: 0 auto !important;
  }
}

/* 针对超小屏幕的额外适配 */
@media (max-width: 320px) {
  .scanning-area {
    width: min(220px, 80vw);
    height: min(220px, 80vw);
  }
  
  .corner-element {
    width: 16px;
    height: 16px;
  }
  
  .scanning-tip {
    font-size: 12px;
    padding: 8px 12px;
  }
  
  .upload-btn {
    bottom: max(env(safe-area-inset-bottom, 15px) + 15px, 30px);
  }
}

/* 扫描动画 */
.scanning-area::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 2px;
  background: linear-gradient(to right, transparent, #fff, transparent);
  animation: scan 2s linear infinite;
  z-index: 2;
}

@keyframes scan {
  0% {
    top: 0;
  }
  100% {
    top: 100%;
  }
}

.camera-controls {
  position: absolute;
  top: 20px;
  right: 20px;
  z-index: 3;
}

.switch-camera-btn {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.5);
  border: none;
}
</style> 